#include <iostream>
#include <string>
//#include <regex>
#include <boost/regex.hpp>
#include "ChatClient.hpp"
#include <signal.h>

// Prototypes
int  parse_commandline_options(int argc, char * argv[]);
void usage(char * argv[]);
void start_server(const char * port_string);
void start_client(char * argv[]);
int str2int(const char * str);
int str2int(const char * str, int length);
bool parse_int(const char * str, int min_len, int max_len, int min_val, int max_val);
bool parse_ipv4(boost::cmatch & m);

static std::string PROMPT = "$ >>> ";

static void clean_exit(int unused)
{
    std::cout << std::endl;
    std::cout.flush();
    std::cout << PROMPT << "[Exiting.]" << std::endl;
    exit(0);
}


// Main
int main(int argc, char * argv[])
{
    signal(SIGINT, clean_exit);


    int mode = parse_commandline_options(argc, argv);

    if(0 == mode)
    {
        std::cout << PROMPT << "Entering server mode..." << std::endl;
        start_server(argv[2]); // argv[2] is the port # the server should listen to
    }
    else if (1 == mode)
    {
        std::cout << PROMPT <<"Entered client mode!" << std::endl;
        start_client(argv);
        // argv[2] is the nickname the client will use
        // argv[3] is a string of the IPv4 address of the server in dotted notation
        // argv[4] is the port number of the server
        // argv[5] is the port number of the client

    }
    else
    {
        std::cerr << "  ### Improper inputs detected." << std::endl;
        std::cerr << "" << std::endl;
        std::cerr << "" << std::endl;


        usage(argv);
    }
}

// Function definitions
int parse_commandline_options(int argc, char * argv[])
{
    // This function performs all error checking of commandline input arguments here
    // so that later we don't have to worry about them.
    // argv[0] is the name of our program
    // argv[1] is the mode, either server or client mode
    // argv[i] for i > 1 represents options for  particular mode.
    // So a valid commandline execution would be if argc > 2
    // The minimum required number of arguments coming in from the commandline
    // is 3.
   
    int return_mode = -1;

    boost::regex intRegEx("\\d+");
    boost::regex ipv4RegEx("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})");
    boost::cmatch octets;
    
    if(argc < 3)
    {
        std::cerr << "  # Too few arguments specified." << std::endl;
        usage(argv);
        return_mode = -1;
    } else {
        std::string mode(argv[1]);

        if(mode.compare("-s")==0 && argc == 3)
        {
            // Check to see if argv[2] is a port number.
            if(boost::regex_match(argv[2], intRegEx))
            {
                // Parse to make sure that the input is acceptable. Port number must be between
                // 1024 and 65535

                if( parse_int(argv[2], 4, 5, 1024, 65535) )
                { // Port passes range test
                    return_mode = 0;
                }
                else
                {
                    return_mode = -1;
                }
            }
        }
        else if(mode.compare("-c")==0 && argc == 6)
        {
            // Check to see if we have correct info for our client mode.
            // -c <nick-name> <server-ip> <server-port> <client-port>
            // The nickname can be any arbitrary string, so we should just that last.
            // ServerIP needs to be a dotted IPv4 address
            // server port and client port need to be positive integers between 1024-65535

            int pass = false;

            // Check validity of server port format
            pass = boost::regex_match(argv[4], intRegEx);
            if(pass)
            {
                pass = parse_int(argv[4], 4, 5, 1024, 65535);
            }

            // Check validity of client port format
            pass = pass && boost::regex_match(argv[5], intRegEx);
            if(pass)
            {
                pass = parse_int(argv[5], 4, 5, 1024, 65535);
            }

            // Check validity of server IP address format
            pass = pass && boost::regex_match(argv[3], octets, ipv4RegEx);
            if(pass)
            {
                pass = parse_ipv4(octets);
            }

            if(pass)
            {
                return_mode = 1;
            }
            else
            {
                return_mode = -1;
            }
        }
        else
        {
            std::cerr << "  # Unrecognized program mode specified." << std::endl;
            usage(argv);
            return_mode = -1;
        }
    }
    return return_mode;
}


int str2int(const char * str)
{
    int length = strlen(str);
    return str2int(str, length);
}
int str2int(const char * str, int length)
{
    int digit = -1;
    int sum = 0;
    int multiplier = 1;
    for(int i = length-1; i >= 0; i--)
    {
        digit = str[i]-48; // Convert from ASCII to integer
        sum = sum + multiplier*digit;
        multiplier *= 10;
    }

    return sum;

}


bool parse_int(const char * str, int min_len, int max_len, int min_val, int max_val)
{
    int length = strlen(str);
    bool pass = false;

    if(length < min_len || length > max_len)
    {
        pass = false;
    }
    else
    {
        int sum = str2int(str, length);
        
        if(sum < min_val || sum > max_val)
        {
            pass = false;
        }
        else
        {
            pass = true;
        }
    }

    return pass;
}

bool parse_ipv4(boost::cmatch &m)
{
    // Test each of the octets to make sure they are between 0 and 255

    bool pass = true;

    pass = parse_int(m[1].str().c_str(), 1, 3, 0, 255) && 
           parse_int(m[2].str().c_str(), 1, 3, 0, 255) && 
           parse_int(m[3].str().c_str(), 1, 3, 0, 255) && 
           parse_int(m[4].str().c_str(), 1, 3, 0, 255);
    return pass;
}


void usage(char* argv[])
{
    std::cerr << "Usage: " << argv[0] << " <mode> <options>" << std::endl;
    std::cerr << "  Modes: -s for server mode or -c for client mode "<< std::endl;
    std::cerr << "  Server Mode (-s) Options:" << std::endl;
    std::cerr << " " << argv[0] << " -s <server-port>" << std::endl;
    std::cerr << "  Client Mode (-c) Options:" << std::endl;
    std::cerr << " " << argv[0] << " -c <nick-name> <server-ip> <server-port> <client-port>";

    std::cerr << "" << std::endl;
    std::cerr << "" << std::endl;
    std::cerr << "  Option Descriptions:" << std::endl;
    std::cerr << "     <server-port>:  should be an integer between 1024 and 65535" << std::endl;
    std::cerr << "     <client-port>:  should be an integer between 1024 and 65535" << std::endl;
    std::cerr << "     <server-ip>  :  should be a dotted IPv4 address" << std::endl;
    std::cerr << "     <nick-name>  :  should be a string of characters" << std::endl;
}

void start_server(const char * port_string)
{
    // Convert the port string to an integer to send to the server
    int port = str2int(port_string);

    // Initialize the server -- server is a client with additional functionality
    ChatClient server(port);

    int result = -1;
    
    result = server.start(5); // Attempt to bind at most 5 times

    if(-1 == result)
    {
        std::cerr << "   ### Unable to start server." << std::endl;
        exit(0);
    }

    // If successfully bound, then we need to attempt just listen for incoming messages
    std::cout << PROMPT << "Server Started Successfully." << std::endl;

    server.print_socket_info();

    // Make the server open to a port to listen on designated port.

    while(server.isRunning())
    {
        int recv_size = server.listen();

        if(recv_size > 0)
        {
            server.parse_data();
        }
        else if (recv_size = 0)
        {
            // connection was closed
        }
        else
        {
            // some error occurred
        }
    }
    std::cout << "Server shutdown now." << std::endl;
}

void start_client(char * argv[])
{

    int server_port = str2int(argv[4]);
    int client_port = str2int(argv[5]);
    
    // Initialize the client
    ChatClient client(argv[2], argv[3], server_port, client_port);

    int result = -1;

    result = client.start(5); // Attempt to open the socket for use 5 times before giving up

    if(-1 == result)
    {
        std::cerr << "   ### Unable to start client." << std::endl;
        exit(0);
    }

    // Client Registration Step
    if (client.isRunning())
    {
        std::cout << PROMPT << "Client Started Successfully, attempting to register with the Server." << std::endl;
        client.print_socket_info();
        client.handle_register_client(); // Attempt to register up to 5 times before giving up
    }
    client.print_client_table();
    result = 0;
    while(client.isRunning() && (result != -1))
    {   
        std::cout << PROMPT;
        std::cout.flush();
        // Have the client listen to both PORT and STDIN
        result = client.demultiplex(10);
        // If result = 2, both STDIN and PORT should be checked
        // If result = 1, only PORT should be checked.
        // If result = 0, only STDIN should be checked.
        // If result = -1, then an error occurred, so exit.
        
        switch(result)
        {
            case 0:
                client.process_stdin();
                break;
            case 1:
                client.listen();
                client.parse_data();
                break;
            case 2:
                client.listen();
                client.parse_data(); // Display data prior to processing stdin
                client.process_stdin();
                break;
            default:
                break;
        }

    }

}








































